#include "SDL.h"
#include "Gfx.h"
#include "ResourceMgr.h"
#include "GfxEffects.h"
#include "Button.h"
//#include "Bricks.h"
//#include "LevelIO.h"

// Quit, Load, Save, Test, brush1, brush4, brush9, brush16, hline, vline, fill
#define NBUTTONS (11)

// Externally used in "Button"
SDL_Surface *screen;

static Button *buttons[NBUTTONS];
static Button *button;
static Button *buttonRadio;

Button *findButton (Sint16 x, Sint16 y) {
  for (int k = 0; k < NBUTTONS; k++) {
    if (buttons[k]->focused(x,y)) {
      return buttons[k];
    }
  }
  return NULL;
}

// Main areas of the level editor
static SDL_Rect areaBumper  = { 816, 720, 32, 32 };
static SDL_Rect areaPalette = { 288, 736, 512, 16 };
static SDL_Rect areaSelected = { 480, 656, 32, 32 };
static SDL_Rect areaData = { 32, 64, 960, 480 };

// Used to determine when the event loop should be
// quited
static bool done;

// Simple routine to decide if a point with integer coordinates
// belongs to the area delimited by a rectangle with integer
// coordinates and size
static
bool
inRect (Sint16 x, Sint16 y, SDL_Rect& rect) {
  if ( (x < rect.x) || (x >= rect.x + rect.w)) return false;
  if ( (y < rect.y) || (y >= rect.y + rect.h)) return false;
  return true;
}

// Convenient macros
// Note that none is provided for areaSelected because no
// event is caught in it
#define IN_AREA_BUMPER(x,y)  ( inRect(x,y,areaBumper) )
#define IN_AREA_PALETTE(x,y) ( inRect(x,y,areaPalette) )
#define IN_AREA_DATA(x,y)    ( inRect(x,y,areaData) )
#define IN_AREA_BRUSHES(x,y) ( inRect(x,y,areaBrushes) )

// Contains the editboard image data
static SDL_Surface *editBoard     = NULL;
static SDL_Surface *brickPalette  = NULL;
static SDL_Surface *bumper        = NULL;

// Brushes
static SDL_Surface *brush1      = NULL;
static SDL_Surface *brush4      = NULL;
static SDL_Surface *brush9      = NULL;
static SDL_Surface *brush16     = NULL;
static SDL_Surface *brushHLine  = NULL;
static SDL_Surface *brushVLine  = NULL;

static SDL_Surface *brush; // Current brush

// Used to identify the current brush
typedef enum {
  BRUSH_1,
  BRUSH_4,
  BRUSH_9,
  BRUSH_16,
  BRUSH_HLINE,
  BRUSH_VLINE,
  BRUSH_FILL,
  BRUSH_BUMPER,
  BRUSH_NONE
} BrushType;

static BrushType currentBrushType = BRUSH_NONE;
static long      levelData[30][30];
static long      currentBrickCode = -1;//B_NONE;

static
void
quit (int dummy = 0) {
  done = true;
  faderZoneOut();
}

static
void
load (int dummy = 0) {
  cout << "Level loaded (fake)" << endl;
}

static
void
save (int dummy = 0) {
  cout << "Level saved (fake)" << endl;
}

static
void
test (int dummy = 0) {
  cout << "Test level (fake)" << endl;
}

static
void
setBrush(SDL_Surface *brusharg) {
  brush = brusharg;
}


// Tile the brush data if the brush
// is a tile brush (4,9,16,hline,vline)
static
void
updateBrush () {
  if ((currentBrushType == BRUSH_NONE) ||
      (currentBrushType == BRUSH_BUMPER) || 
      (currentBrushType == BRUSH_1) ||
      (currentBrushType == BRUSH_FILL)) {
    //cout << "Skipping currentBrushType: " << currentBrushType << endl;
    return;
  }
  int nc = brush->w / brush1->w;
  int nr = brush->h / brush1->h;

  SDL_Rect src = { 0, 0,   brush1->w, brush1->h };
  SDL_Rect dst = { -1, -1, src.w, src.h };
  for (int r = 0; r < nr; r++) {
    dst.y = r * brush1->h;
    for (int c = 0; c < nc; c++) {
      dst.x = c * brush1->w;
      SDL_BlitSurface (brush1, &src, brush, &dst);
    }
  }
}

static
void
updateBrickCode (int tile) {
    if (tile <= 0x000C) {
        // It's a brick
        currentBrickCode = tile;
    }
    else {
        // It's a bonus
        tile -= 0x000C;
        currentBrickCode = 0x0100 * tile;
    }
    printf ("New brick code: %x\n", currentBrickCode);
}

static
void
selectTile (Sint16 x, Sint16 y) {
  Sint16 xx = x - areaPalette.x;
  Sint16 yy = y - areaPalette.y;
  int tile = (double)xx / 32.0;
  SDL_Rect src, dst;
  src.x = tile * 32;
  src.y = 0;
  src.w = 32;
  src.h = 16;
  dst.x = areaSelected.x;
  dst.y = areaSelected.y+8;;
  dst.w = src.w;
  dst.h = src.h;
  // Avoid refresh troubles when a bigger object was selected before
  SDL_BlitSurface (editBoard, &areaSelected, screen, &areaSelected);
  SDL_BlitSurface (brickPalette, &src, screen, &dst);
  SDL_UpdateRects (screen, 1, &areaSelected);

  // Update the brush data
  dst.x = 0;
  dst.y = 0;
  SDL_BlitSurface (brickPalette, &src, brush1, &dst);

  // Update the current brush by tiling the data (if needed)
  updateBrush();
  // Update the brick code (warning: indices start at 1)
  updateBrickCode(tile+1);
}

static
void
selectBumper () {
  // Avoid refresh troubles when a bigger object was selected before
  SDL_BlitSurface (editBoard, &areaSelected, screen, &areaSelected);
  //  SDL_BlitSurface (screen, &areaBumper, brush4, &rect);
  SDL_BlitSurface (bumper, NULL, screen, &areaSelected);
  SDL_UpdateRects (screen, 1, &areaSelected);

  // The bumper tool doesn't allow brush specification
  // by the user.
  if (buttonRadio != NULL) {
    buttonRadio->release();
    buttonRadio = NULL;
  }
  setBrush(bumper);
  currentBrushType = BRUSH_BUMPER;
  //currentBrickCode = B_BUMPER;
  printf ("New brick code: %x\n", currentBrickCode);
}

static
void
eventLoop (void) {
  SDL_Event evt;

  done = false;

  while (!done) {
    while (SDL_PollEvent(&evt) > 0) {
      switch (evt.type) {
        case SDL_QUIT:
          quit();
          break;
        case SDL_KEYDOWN:
          if (evt.key.keysym.sym == SDLK_q) {
            quit();
          }
          break;
        case SDL_MOUSEMOTION:
          {
            Sint16 X = evt.motion.x;
            Sint16 Y = evt.motion.y;
            if ((button != NULL) && (!button->isRadio())) {
              button->release();
              button = NULL;
            }
            if (IN_AREA_DATA(X,Y)) {
              //cout << "DATA" << endl;
            }
          }
          break;
        case SDL_MOUSEBUTTONDOWN:
          {
            Sint16 X = evt.motion.x;
            Sint16 Y = evt.motion.y;
            if (IN_AREA_BUMPER(X,Y)) {
              //cout << "BUMPER" << endl;
              selectBumper();
              break;
            }
            if (IN_AREA_PALETTE(X,Y)) {
              //cout << "PALETTE" << endl;
              selectTile(X,Y);
              break;
            }
            if (IN_AREA_DATA(X,Y)) {
              //cout << "DATA" << endl;
              // $$ DEBUG ONLY
              if (brush != NULL) {
                SDL_Rect src = { 0, 0, -1, -1 };
                int rx, ry, gridx, gridy;
                if (brush == bumper) {
                  rx = (double)X / 32.0;
                  ry = (double)Y / 32.0;
                  gridx = rx * 32;
                  gridy = ry * 32;
                }
                else {
                  rx = (double)X / 32.0;
                  ry = (double)Y / 16.0;
                  gridx = rx * 32;
                  gridy = ry * 16;
                }
                SDL_Rect dst = { gridx, gridy, 0, 0 };
		src.w = dst.w = (gridx + brush->w > 992) ? (992 - gridx) : brush->w;
                src.h = dst.h = (gridy + brush->h > 544) ? (544 - gridy) : brush->h;
                SDL_BlitSurface (brush, &src, screen, &dst);
                SDL_UpdateRects (screen, 1, &dst);
              }
              break;
            }
            button = findButton(X,Y);
            if (button != NULL) {
              button->push();
              if (button->isRadio()) {
                if (button->isPushed()) {
                  buttonRadio = button;
                }
                else {
                  buttonRadio = NULL;
                }
              }
            }
          }
          break;
        case SDL_MOUSEBUTTONUP:
          {
            Sint16 X = evt.motion.x;
            Sint16 Y = evt.motion.y;
            if (button != NULL) {
              if (!button->isRadio()) {
                button->release();
                button->callback();
                button = NULL;
              }
            }
          }
          break;
      }
    }
  }
}

static
void
setBrush(int size) {
  cout << "Brush size set to " << size << endl;
  switch(size) {
    case 1:
      currentBrushType = BRUSH_1;
      setBrush(brush1);
      break;
    case 4:
      currentBrushType = BRUSH_4;
      setBrush(brush4);
      break;
    case 9:
      currentBrushType = BRUSH_9;
      setBrush(brush9);
      break;
    case 16:
      currentBrushType = BRUSH_16;
      setBrush(brush16);
      break;
  }
  updateBrush();
}

static
void
setBrushHLine (int dummy = 0) {
  cout << "Brush HLine selected" << endl;
  currentBrushType = BRUSH_HLINE;
  setBrush(brushHLine);
  updateBrush();
}

static
void
setBrushVLine (int dummy = 0) {
  cout << "Brush VLine selected" << endl;
  currentBrushType = BRUSH_VLINE;
  setBrush(brushVLine);
  updateBrush();
}

static
void
setBrushFill (int dummy = 0) {
  cout << "Brush fill selected" << endl;
  currentBrushType = BRUSH_FILL;
  brush = NULL;
  // It's not really a brush but rather
  // a tool.
}

static
void
createBrushes (void) {
  // "true" means "place surface in hardware memory"
  brush1  = Gfx::instance()->createSurface(32,16,true);
  brush4  = Gfx::instance()->createSurface(64,32,true);
  brush9  = Gfx::instance()->createSurface(96,48,true);
  brush16 = Gfx::instance()->createSurface(128,64,true);
  brushHLine = Gfx::instance()->createSurface(960,16,true);
  brushVLine = Gfx::instance()->createSurface(32,480,true);

  SDL_SetColorKey(brush1, SDL_SRCCOLORKEY, 0);
  SDL_SetColorKey(brush4, SDL_SRCCOLORKEY, 0);
  SDL_SetColorKey(brush9, SDL_SRCCOLORKEY, 0);
  SDL_SetColorKey(brush16, SDL_SRCCOLORKEY, 0);
  SDL_SetColorKey(brushHLine, SDL_SRCCOLORKEY, 0);
  SDL_SetColorKey(brushVLine, SDL_SRCCOLORKEY, 0);

  // no brush selected yet !
  brush = NULL;
}

static
void
createButtons (void) {
  ResourceMgr *rmgr = ResourceMgr::instance();
  // Control buttons
  buttons[0] = new Button(rmgr->getGfx("quitPushed.png",false),
      screen, 0, 720, 64, 32,
      0, 720, quit);
  buttons[1] = new Button(rmgr->getGfx("savePushed.png",false),
      screen, 64, 720, 64, 32,
      64, 720, save);
  buttons[2] = new Button(rmgr->getGfx("loadPushed.png",false),
      screen, 128, 720, 64, 32,
      128, 720, load);
  buttons[3] = new Button(rmgr->getGfx("testPushed.png",false),
      screen, 192, 720, 64, 32,
      192, 720, test);
  // Brush buttons
  buttons[4] = new Button(rmgr->getGfx("brush1Pushed.png",false),
      screen, 912, 560, 16, 16,
      912, 560, setBrush, 1);
  buttons[5] = new Button(rmgr->getGfx("brush4Pushed.png",false),
      screen, 912, 576, 16, 16,
      912, 576, setBrush, 4);
  buttons[6] = new Button(rmgr->getGfx("brush9Pushed.png",false),
      screen, 912, 592, 16, 16,
      912, 592, setBrush, 9);
  buttons[7] = new Button(rmgr->getGfx("brush16Pushed.png",false),
      screen, 912, 608, 16, 16,
      912, 608, setBrush, 16);
  buttons[8] = new Button(rmgr->getGfx("hlinePushed.png",false),
      screen, 912, 624, 16, 16,
      912, 624, setBrushHLine);
  buttons[9] = new Button(rmgr->getGfx("vlinePushed.png",false),
      screen, 912, 640, 16, 16,
      912, 640, setBrushVLine);
  buttons[10] = new Button(rmgr->getGfx("fillPushed.png",false),
      screen, 912, 656, 16, 16,
      912, 656, setBrushFill);

  for (int k = 4; k <= 10; k++) {
    buttons[k]->setRadioGroup(0);
  }
}

int main (int argc, char **argv) {
  // Initializes SDL prior to setting up a display
  Gfx *gfx = Gfx::instance();

  //#ifdef __DARWIN__
  gfx->init(1024, 768, 0, GFX_WINDOWED);

  screen = gfx->instance()->getScreen();

  ResourceMgr *rmgr = ResourceMgr::instance();
  rmgr->setRootPath("./data");

  // Load gfx
  editBoard = rmgr->getGfx("editboard.png", false /*Don't put in cache*/);
  brickPalette = rmgr->getGfx("bricks_32x16.png", false);
  bumper = rmgr->getGfx("bumper.png", false);

  // Blit the edit board
  SDL_Rect rect = { 0, 0, 1024, 768 };

  SDL_BlitSurface(editBoard, &rect, screen, &rect);

  rect.w = bumper->w;
  rect.h = bumper->h;
  SDL_BlitSurface (bumper, &rect, screen, &areaBumper);
  rect.w = brickPalette->w;
  rect.h = brickPalette->h;
  SDL_BlitSurface (brickPalette, NULL, screen, &areaPalette);

  SDL_UpdateRect(screen, 0, 0, 1024, 768);


  // Necessarily after blitting the editboard
  createBrushes();
  createButtons();

  // Enter GUI event loop
  eventLoop(); 

  rmgr->kill();
  gfx->kill();

  exit (EXIT_SUCCESS);
}

